P06-2. 실제 Linux VM 하이브리드 서비스 메시 - 실습 검증

Part 06 (실습): 실제 Linux VM 하이브리드 서비스 메시 검증

실습 목표

  1. 쿠버네티스 클러스터 바깥의 Docker kind 브릿지 네트워크(172.19.0.0/16)에 실제 PostgreSQL 18 Linux VM 컨테이너(study-pg-vm) 를 기동하고 사설 IP(172.19.0.x)를 할당받는다.
  2. WorkloadGroup · WorkloadEntry · ServiceEntry · Service 4종 세트를 배포하여 외부 Linux VM을 Ambient Mesh의 정식 워크로드 및 서비스로 편입시킨다.
  3. 쿠버네티스 내부의 pg-client 파드에서 ztunnel 내장 DNS 프록시(127.0.0.1:15053)가 응답해 주는 가상 IP(240.240.0.50)와 HBONE(:15008) 터널을 타고 pg-vm.mesh.internal:5432 외부 DB VM에 접속해 실제 테이블 생성 및 데이터 저장(INSERT/SELECT) 을 수행한다.
  4. 설령 해커가 DB 비밀번호(meshpass)를 탈취했더라도, 허가되지 않은 파드(rogue-db-client)가 외부 DB VM에 접속하려 할 때 Waypoint Proxy가 SPIFFE 신분증을 검사해 DB 앞단에서 연결을 즉시 차단하는 하이브리드 Zero-Trust 보안을 실측 증명한다.

사전 환경 확인

  • P06-1 이론 개념을 먼저 읽고 오면 WorkloadGroup(VM용 Deployment), WorkloadEntry(VM용 Pod), ServiceEntry(커스텀 전화번호부)의 1:1 매핑 구조가 명확히 이해됩니다.
  • Windows PowerShell 창을 열고 아래 명령어로 현재 클러스터 상태를 확인합니다.
    $env:Path += ";$HOME\bin"
    cd "$HOME\mesh-study"
    kubectl get nodes -o wide
    


실습 아키텍처 및 하이브리드 연동 구조 한눈에 보기

flowchart LR
    subgraph K8S_CLUSTER["Kubernetes 클러스터 내부 (part1-demo 네임스페이스)"]
        PG_GOOD["pg-client 파드
(신분: sa/default)
PGPASSWORD=meshpass"] PG_BAD["rogue-db-client 파드
(침입자: sa/rogue-sa)
PGPASSWORD=meshpass (비번 탈취!)"] ZT["Node ztunnel
- 내장 DNS (:15053) -> 240.240.0.50 응답
- TCP :15001 납치 -> HBONE (:15008)"] WP["Waypoint Proxy
- AuthorizationPolicy 검문!
- WorkloadEntry(172.19.0.7)로 라우팅"] end subgraph EXT_NET["클러스터 외부 · Docker kind 브릿지 네트워크 (172.19.0.0/16)"] VM["study-pg-vm (외부 Linux VM)
PostgreSQL 18.6 (IP: 172.19.0.7:5432)
DB: ordersdb"] end PG_GOOD -->|"psql -h pg-vm.mesh.internal"| ZT PG_BAD -->|"psql -h pg-vm.mesh.internal"| ZT ZT ==>|"HBONE (:15008)"| WP WP ==>|"1. sa/default 통과! -> SQL 실행"| VM WP -.->|"2. sa/rogue-sa 즉시 연결 차단!
(DB 비번이 맞아도 접근 불가!)"| PG_BAD

Step 1. 클러스터 외부에 실제 PostgreSQL 18 Linux VM(study-pg-vm) 기동

Docker Desktop 내장 3노드 클러스터(desktop-control-plane, desktop-worker, desktop-worker2)는 Docker의 kind 브릿지 네트워크(172.19.0.0/16) 위에 연결되어 있습니다.
실무에서 쿠버네티스 노드들과 같은 VPC 서브넷(또는 Peering 네트워크)에 떠 있는 외부 Linux DB VM을 완벽히 재현하기 위해, 쿠버네티스 파드가 아닌 독립된 Docker 컨테이너(study-pg-vm) 를 --network kind에 직접 띄웁니다!

# 1. 기존 컨테이너가 있다면 정리 후, kind 브릿지 네트워크에 외부 PostgreSQL 18 VM 기동
docker rm -f study-pg-vm 2>$null
docker run -d --name study-pg-vm --network kind `
  -e POSTGRES_USER=meshuser `
  -e POSTGRES_PASSWORD=meshpass `
  -e POSTGRES_DB=ordersdb `
  postgres:18-alpine

# 2. DB 초기화를 위해 4초 대기 후, 할당된 외부 VM의 사설 IP(172.19.0.x) 추출!
Start-Sleep -Seconds 4
$vmIP = docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" study-pg-vm
Write-Output "External VM IP on kind network: $vmIP"

실제 출력 결과:

8539b71586a91ed9c3003d99c667dcdefac674ecd715261a1723de4b62bde9ff
External VM IP on kind network: 172.19.0.7

Step 2. 외부 Linux VM을 서비스 메시의 정식 가족으로 호적 등재 (WorkloadEntry & ServiceEntry)

이제 위에서 알아낸 $vmIP(172.19.0.7)를 이용해 WorkloadGroup, WorkloadEntry, ServiceEntry, 그리고 쿠버네티스 표준 Service(external-pg) 를 한 번에 등록합니다.

# part6-hybrid-vm.yaml 생성 및 적용 ($vmIP 변수가 자동으로 실제 IP로 치환되어 들어갑니다!)
@"
apiVersion: networking.istio.io/v1
kind: WorkloadGroup
metadata:
  name: pg-vm-group
  namespace: part1-demo
spec:
  metadata:
    labels:
      app: external-pg-vm
      class: vm
  template:
    serviceAccount: default
    network: vm-network
---
apiVersion: networking.istio.io/v1
kind: WorkloadEntry
metadata:
  name: study-pg-vm-01
  namespace: part1-demo
spec:
  address: $vmIP
  labels:
    app: external-pg-vm
    class: vm
    instance: study-pg-vm-01
  serviceAccount: default
---
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: external-pg-se
  namespace: part1-demo
spec:
  hosts:
  - pg-vm.mesh.internal
  addresses:
  - 240.240.0.50
  ports:
  - number: 5432
    name: tcp-postgres
    protocol: TCP
  location: MESH_INTERNAL
  resolution: STATIC
  workloadSelector:
    labels:
      app: external-pg-vm
---
apiVersion: v1
kind: Service
metadata:
  name: external-pg
  namespace: part1-demo
spec:
  selector:
    app: external-pg-vm
  ports:
  - name: tcp-postgres
    port: 5432
    targetPort: 5432
"@ | Set-Content -Path "$HOME\mesh-study\part6-hybrid-vm.yaml" -Encoding utf8

kubectl apply -f "$HOME\mesh-study\part6-hybrid-vm.yaml"

실제 출력 결과:

workloadgroup.networking.istio.io/pg-vm-group created
workloadentry.networking.istio.io/study-pg-vm-01 created
serviceentry.networking.istio.io/external-pg-se created
service/external-pg created

ztunnel 장부에 외부 VM이 어떻게 등록되었는지 확인!

과연 노드의 ztunnel이 클러스터 바깥의 study-pg-vm-01(172.19.0.7)을 파드 목록(workloads)과 서비스 목록(services)에 정식으로 올려놓았는지 확인합니다.

# 1. ztunnel이 인식한 서비스 목록 (external-pg 와 external-pg-se 확인!)
istioctl ztunnel-config services --service-namespace part1-demo

# 2. ztunnel이 인식한 워크로드 목록 (study-pg-vm-01 확인!)
istioctl ztunnel-config workloads --workload-namespace part1-demo

실제 출력 결과:

NAMESPACE  SERVICE NAME   SERVICE VIP   WAYPOINT ENDPOINTS
part1-demo external-pg    10.96.75.26   waypoint 1/1
part1-demo external-pg-se 240.240.0.50  waypoint 1/1
part1-demo order-api      10.96.72.69   waypoint 3/3
part1-demo order-api-v1   10.96.72.172  waypoint 2/2
part1-demo order-api-v2   10.96.242.219 waypoint 1/1
part1-demo waypoint       10.96.27.87   None     1/1

NAMESPACE  POD NAME                      ADDRESS     NODE            WAYPOINT PROTOCOL
part1-demo order-api-86d8bb68dd-mkvrw    10.244.2.6  desktop-worker  None     HBONE
part1-demo order-api-86d8bb68dd-xh5rm    10.244.1.8  desktop-worker2 None     HBONE
part1-demo order-api-v2-677cff8fd5-wz8sp 10.244.2.10 desktop-worker  None     HBONE
part1-demo study-client                  10.244.2.7  desktop-worker  None     HBONE
part1-demo study-pg-vm-01                172.19.0.7                  None     TCP
part1-demo waypoint-6cc777d4c7-cwlx2     10.244.2.8  desktop-worker  None     TCP
ztunnel 장부에서 확인되는 3가지 핵심 포인트!

  1. POD NAME: study-pg-vm-01 | ADDRESS: 172.19.0.7: 쿠버네티스 파드가 아닌 외부 Docker 컨테이너 VM인데도 ztunnel의 워크로드 장부에 일반 파드들과 나란히 등재되었습니다! (노드 밖의 외부 VM이므로 NODE 칸은 비어 있고 PROTOCOL: TCP로 표시됩니다.)
  2. external-pg-se | 240.240.0.50 | waypoint | 1/1: ServiceEntry로 만든 커스텀 도메인(pg-vm.mesh.internal)이 Waypoint와 연결되었고 살아있는 엔드포인트 1/1(study-pg-vm-01) 을 정상 인식했습니다!
  3. external-pg | 10.96.75.26 | waypoint | 1/1: 일반 쿠버네티스 Service도 라벨 셀렉터(app: external-pg-vm)를 통해 외부 VM을 자기 엔드포인트(1/1)로 자동 연결했습니다!


Step 3. 쿠버네티스 파드에서 외부 PostgreSQL 18 VM으로 실제 SQL 쿼리 실행!

이제 쿠버네티스 내부(part1-demo)에 PostgreSQL 클라이언트 파드(pg-client)를 하나 띄우고, IP 주소(172.19.0.7)를 전혀 몰라도 메시 도메인(pg-vm.mesh.internal 및 external-pg.part1-demo.svc.cluster.local)만으로 외부 DB VM에 접속해 실제 SQL 쿼리가 실행되는지 확인합니다!

# 1. K8s 클러스터 내부에 PostgreSQL 클라이언트 파드(pg-client) 기동
kubectl run pg-client -n part1-demo --image=postgres:18-alpine --restart=Never --env="PGPASSWORD=meshpass" -- sleep 3650d
kubectl wait --for=condition=Ready pod/pg-client -n part1-demo --timeout=90s

# 2. ServiceEntry 커스텀 도메인(pg-vm.mesh.internal)으로 외부 VM에 접속해 DB 버전 및 실제 서버 IP 조회!
kubectl exec -n part1-demo pg-client -- psql -h pg-vm.mesh.internal -U meshuser -d ordersdb -c "SELECT version(), current_database(), inet_server_addr(), inet_server_port();"

# 3. K8s 표준 서비스 도메인(external-pg.part1-demo.svc.cluster.local)으로 접속해 주문 테이블 생성 및 INSERT!
kubectl exec -n part1-demo pg-client -- psql -h external-pg.part1-demo.svc.cluster.local -U meshuser -d ordersdb -c "CREATE TABLE IF NOT EXISTS mesh_orders (id SERIAL PRIMARY KEY, item TEXT, created_at TIMESTAMPTZ DEFAULT now()); INSERT INTO mesh_orders (item) VALUES ('Hybrid Mesh VM Order #1') RETURNING *;"

실제 출력 결과:

pod/pg-client created
pod/pg-client condition met

                                         version                                         | current_database | inet_server_addr | inet_server_port 
-----------------------------------------------------------------------------------------+------------------+------------------+------------------
 PostgreSQL 18.6 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit | ordersdb         | 172.19.0.7       |             5432
(1 row)

CREATE TABLE
 id |          item           |          created_at           
----+-------------------------+-------------------------------
  1 | Hybrid Mesh VM Order #1 | 2026-10-25 07:16:59.663021+00
(1 row)

INSERT 0 1

Step 4. 하이브리드 Zero-Trust 보안 — DB 비밀번호가 털려도 Waypoint가 외부 DB를 지킨다!

전통적인 환경에서는 해커가 클러스터 안의 파드 하나를 뚫고 환경변수에 적힌 DB 비밀번호(PGPASSWORD=meshpass) 를 훔쳐내면, 그 파드에서 외부 DB VM으로 접속해 모든 고객 데이터를 탈취할 수 있었습니다.

하지만 외부 DB VM을 우리 서비스 메시에 편입시킨 지금, AuthorizationPolicy로 "오직 허가된 서비스 계정(sa/default)만 외부 DB(external-pg / external-pg-se)의 5432 포트에 접속할 수 있다!" 고 지정하면 어떻게 될까요?
똑같은 진짜 DB 비밀번호(meshpass)를 가진 침입자 파드(rogue-db-client, 계정: rogue-sa) 를 띄워 직접 실험해 봅니다!

# 1. 진짜 DB 비밀번호(meshpass)를 알고 있는 침입자 파드(rogue-db-client)와 외부 DB 보호 정책(external-pg-guard) 배포!
@"
apiVersion: v1
kind: Pod
metadata:
  name: rogue-db-client
  namespace: part1-demo
spec:
  serviceAccountName: rogue-sa
  containers:
  - name: psql
    image: postgres:18-alpine
    command: ["sleep", "3650d"]
    env:
    - name: PGPASSWORD
      value: "meshpass"
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: external-pg-guard
  namespace: part1-demo
spec:
  targetRefs:
  - kind: Service
    group: ""
    name: external-pg
  - kind: ServiceEntry
    group: networking.istio.io
    name: external-pg-se
  action: ALLOW
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/part1-demo/sa/default"]
    to:
    - operation:
        ports: ["5432"]
"@ | kubectl apply -f -

kubectl wait --for=condition=Ready pod/rogue-db-client -n part1-demo --timeout=60s

# 2. 허가된 파드(pg-client, sa/default)에서 외부 DB 조회 -> 정상 성공!
kubectl exec -n part1-demo pg-client -- psql -h pg-vm.mesh.internal -U meshuser -d ordersdb -c "SELECT * FROM mesh_orders;"

# 3. 비밀번호를 훔친 침입자 파드(rogue-db-client, sa/rogue-sa)에서 외부 DB 조회 -> Waypoint가 즉시 차단!
kubectl exec -n part1-demo rogue-db-client -- psql -h pg-vm.mesh.internal -U meshuser -d ordersdb -c "SELECT * FROM mesh_orders;"

실제 출력 결과:

# [허가된 pg-client 결과: 정상 조회 성공!]
 id |          item           |          created_at           
----+-------------------------+-------------------------------
  1 | Hybrid Mesh VM Order #1 | 2026-10-25 07:16:59.663021+00
(1 row)

# [침입자 rogue-db-client 결과: DB 비밀번호가 맞아도 Waypoint가 연결 즉시 강제 종료!]
psql: error: connection to server at "pg-vm.mesh.internal" (240.240.0.50), port 5432 failed: server closed the connection unexpectedly
	This probably means the server terminated abnormally
	before or while processing the request.
command terminated with exit code 2
외부 PostgreSQL VM은 손끝 하나 안 건드렸는데 완벽한 DB 방화벽 완성!

  • 우리는 외부 study-pg-vm 컨테이너의 pg_hba.conf나 리눅스 방화벽을 전혀 건드리지 않았습니다.
  • 심지어 침입자 파드(rogue-db-client)는 정확한 DB 아이디(meshuser)와 비밀번호(meshpass) 를 들고 접속했습니다.
  • 그럼에도 불구하고 패킷이 외부 VM으로 나가기 직전 Waypoint Proxy가 침입자 파드의 SPIFFE 전자 여권(sa/rogue-sa)을 적발하고 TCP 연결을 그 자리에서 끊어버렸습니다(server closed the connection unexpectedly)! 이것이 바로 3세대 하이브리드 서비스 메시가 실무 인프라(RDS/Cloud DB 연동)에서 발휘하는 진정한 위력입니다.

다음 파트(Part 07 PostgreSQL 심화)를 위한 정리

침입자 파드와 임시 차단 정책만 지우고, 외부 PostgreSQL 18 VM(study-pg-vm)과 하이브리드 메시 연결(pg-vm.mesh.internal)은 곧바로 이어질 Part 07(PostgreSQL 내부 아키텍처 — MVCC, Lock, pg_stat_statements)에서 그대로 사용하므로 유지합니다!

kubectl delete pod rogue-db-client -n part1-demo --wait=false
kubectl delete authorizationpolicy external-pg-guard -n part1-demo

Step 5. 자주 발생하는 문제와 해결 (Troubleshooting)

증상 / 에러 메시지 원인 해결 방법
psql -h pg-vm.mesh.internal 실행 시 could not translate host name "pg-vm.mesh.internal" to address 에러 1) ServiceEntry에 addresses: ["240.240.0.50"] 가상 IP를 지정하지 않았거나,
2) 클라이언트 파드가 Ambient Mesh(istio.io/dataplane-mode=ambient)에 편입되지 않아 ztunnel DNS(127.0.0.1:15053)를 타지 않음
ServiceEntry에 고정 가상 IP(240.240.x.x)를 명시하고, 클라이언트 파드가 속한 네임스페이스에 istio.io/dataplane-mode=ambient 라벨이 있는지 확인
study-pg-vm 컨테이너를 다시 띄웠더니 연결이 타임아웃됨 컨테이너를 삭제 후 재생성할 때 Docker 브릿지 IP(172.19.0.x)가 다른 번호로 바뀌었을 수 있음 docker inspect로 새 IP를 확인한 뒤 WorkloadEntry(study-pg-vm-01)의 spec.address를 업데이트
ServiceEntry에 AuthorizationPolicy를 걸었는데 차단이 안 됨 targetRefs에 ServiceEntry를 지정할 때 group: networking.istio.io를 빠뜨렸거나 네임스페이스에 Waypoint가 없음 targetRefs에 group: networking.istio.io, kind: ServiceEntry를 정확히 명시하고 Waypoint가 동작 중인지 확인

Step 6. 이번 파트 핵심 명령어 정리 (Command Reference)

# 1. Docker kind 네트워크에 연결된 외부 VM 컨테이너의 사설 IP 확인
docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" study-pg-vm

# 2. 클러스터에 등록된 하이브리드 메시 리소스(WorkloadGroup, WorkloadEntry, ServiceEntry) 확인
kubectl get workloadgroup,workloadentry,serviceentry -n part1-demo

# 3. K8s 내부 파드에서 메시 도메인(pg-vm.mesh.internal)으로 외부 PostgreSQL 18 VM 쿼리 실행
kubectl exec -n part1-demo pg-client -- psql -h pg-vm.mesh.internal -U meshuser -d ordersdb -c "SELECT * FROM mesh_orders;"

Step 7. 셀프 체크 (Self-Check Q&A)

Q1. 쿠버네티스의 Deployment, Pod, Service에 각각 1:1로 대응하는 Istio의 외부 VM 연동 리소스 3가지는 무엇인가요?

Q2. 쿠버네티스 기본 CoreDNS에는 pg-vm.mesh.internal 도메인이 등록되어 있지 않은데, pg-client 파드는 어떻게 이 도메인의 IP(240.240.0.50)를 알아냈나요?


🔗 다음 파트로 이동

축하합니다! Part 01부터 Part 06까지 달려오며 우리는 1세대 Sidecar 메시 → 2세대 Ambient Mesh (ztunnel + Waypoint) → Zero-Trust 보안 (SPIFFE/SVID) → L7 트래픽 제어 & 서킷 브레이커 → 3세대 하이브리드 Linux VM 연동 (PostgreSQL 18) 까지 서비스 메시의 전 세대를 100% 라이브 클러스터 위에서 정복했습니다!

다음 Part 07에서는 방금 하이브리드 메시에 연결한 PostgreSQL 18 (study-pg-vm)의 내부 아키텍처(Process-per-connection, MVCC, Row Lock 경합, pg_stat_statements)와 Terraform IaC 설계를 파헤치고, Part 08~12에서 대망의 풀스택 LGTM 관측 스택(Grafana Alloy + Loki + Grafana + Tempo + Mimir/VictoriaMetrics + Kiali) 을 이 위에 그대로 얹어냅니다!